﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Linq.Expressions;
using System.Diagnostics;
using System.Runtime.Serialization;

namespace GoodStuff.Diagnostics
{
    /// <summary>
    /// Extensionmethods for display information about Exceptions
    /// </summary>
    public static class ExceptionExtensions
    {
        /// <summary>
        /// Returns the differtent parts of the stacktrace with the given lineseperator
        /// </summary>
        /// <param name="exception"></param>
        /// <param name="lineSeparator"></param>
        /// <returns></returns>
        public static string ToString(this Exception exception, string lineSeparator)
        {
            StringBuilder sb = new StringBuilder();
            exception.ToString(sb, lineSeparator);
            return sb.ToString();
        }

        /// <summary>
        /// Returns the differtent parts of the stacktrace with the given lineseperator based on the referenced StringBuilder
        /// </summary>
        /// <param name="exception"></param>
        /// <param name="sb"></param>
        /// <param name="lineSeparator"></param>
        public static void ToString(this Exception exception, StringBuilder sb, string lineSeparator)
        {      
            if (exception.InnerException != null)
            {
                sb.Append(exception.InnerException.ToString(lineSeparator));
                sb.Append(lineSeparator);
            }

            sb.Append("[");
            sb.Append(exception.GetType().FullName);
            sb.Append(": ");
            sb.Append(exception.Message);
            sb.Append("]");
            sb.Append(lineSeparator);
            sb.Append(exception.StackTrace);
            sb.Append(lineSeparator);
            if (exception.Data != null && exception.Data.Count > 0)
            {                
                sb.Append(lineSeparator);
                foreach (DictionaryEntry data in exception.Data)
                {
                    sb.Append(string.Concat("    ", data.Key, " = ", data.Value.ToString()));
                    sb.Append(lineSeparator);
                }
            }
        }

        /// <summary>
        /// Adds name and the value of a variable to an existing Exception. It also includes the name of currently executing method.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="exception"></param>
        /// <param name="propertySelector"></param>
        public static void AddData<T>(this Exception exception, Expression<Func<T>> propertySelector)
        {
            //get the name of the expression or property.
            string parameterName = propertySelector.Body.ToString();

            var memberExpression = propertySelector.Body as MemberExpression;
            if (memberExpression != null)
            {
                parameterName = (memberExpression != null) ? memberExpression.Member.Name : string.Empty;
            }

            //get the value of the expression.
            Func<T> f = propertySelector.Compile();
            T value = f.Invoke();

            //get the current method name.
            StackTrace st = new StackTrace();
            StackFrame frame = st.GetFrame(1); //get the parent frame, not this one.

            string className = frame.GetMethod().DeclaringType.FullName;
            string methodName = frame.GetMethod().Name;

            if (value is ISerializable)
            {
                exception.Data.Add(string.Concat(className, ".", methodName, ".", parameterName), value);
            }
            else
            {
                exception.Data.Add(string.Concat(className, ".", methodName, ".", parameterName), value.ToString());
            }            
        }

        /// <summary>
        /// Adds a name and a value of a variable to an existing Exception. It also includes the name of currently executing method.
        /// </summary>
        /// <param name="exception"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public static void AddData(this Exception exception, string name, object value)
        {
            //get the current method name.
            StackTrace st = new StackTrace();
            StackFrame frame = st.GetFrame(1); //get the parent frame, not this one.

            string className = frame.GetMethod().DeclaringType.FullName;
            string methodName = frame.GetMethod().Name;

            if (!(value is ISerializable))
            {
                value = value.ToString();
            }

            exception.Data.Add(string.Concat(className, ".", methodName, ".", name), value);
        }
    }
}
